iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
Mobile Development

用Flutter Flame做遊戲!Live!系列 第 9

Forge2D真是讓人蛋疼啊!

  • 分享至 

  • xImage
  •  

以下這個官方範例...
為什麼會忽然拿出官方範例呢?
主要原因是因為Forge2D跟Collision不一樣,它並不支援一般Component,而是支援自己獨有的BodyComponent,而BodyComponent有著很複雜的建構子...不會用啊!沒辦法用「將現有的Component改為繼承擴充BodyComponent後直接開始測試」,所以只好乖乖地拿出官方範例來研究了!

(謹記:不要以為將現有方案升級自新系統新框架很簡單,那都是套件開發者不負責任的自吹自擂。該升級時還是要升級,但千萬不要有「這看起來好像很輕鬆」的妄想。)


以下這個官方範例就是將整個視窗做成四面牆,然後有顆球會在裡面隨意的彈跳碰撞。


class MyGame4 extends Forge2DGame {

  //FlowerComponent square = FlowerComponent();

  @override
  Future<void> onLoad() async {
    await super.onLoad();

    //square.width = 20;
    //square.height = 20;
    //world.add(square);

    camera.viewport.add(FpsTextComponent());
    world.add(Ball());
    world.addAll(createBoundaries());
  }

  List<Component> createBoundaries() {
    final visibleRect = camera.visibleWorldRect;
    final topLeft = visibleRect.topLeft.toVector2();
    final topRight = visibleRect.topRight.toVector2();
    final bottomRight = visibleRect.bottomRight.toVector2();
    final bottomLeft = visibleRect.bottomLeft.toVector2();

    return [
      Wall(topLeft, topRight),
      Wall(topRight, bottomRight),
      Wall(bottomLeft, bottomRight),
      Wall(topLeft, bottomLeft),
    ];
  }

}

class Ball extends BodyComponent with TapCallbacks {
  Ball({Vector2? initialPosition})
      : super(
    fixtureDefs: [
      FixtureDef(
        CircleShape()..radius = 5,
        restitution: 0.8,
        friction: 0.4,
      ),
    ],
    bodyDef: BodyDef(
      angularDamping: 0.8,
      position: initialPosition ?? Vector2.zero(),
      type: BodyType.dynamic,
    ),
  );

  @override
  void onTapDown(_) {
    body.applyLinearImpulse(Vector2.random() * 5000);
  }
}

class Wall extends BodyComponent {
  final Vector2 _start;
  final Vector2 _end;

  Wall(this._start, this._end);

  @override
  Body createBody() {
    final shape = EdgeShape()..set(_start, _end);
    final fixtureDef = FixtureDef(shape, friction: 0.3);
    final bodyDef = BodyDef(
      position: Vector2.zero(),
    );

    return world.createBody(bodyDef)..createFixture(fixtureDef);
  }
}

World是Flame引擎預設的一層Component,用來方便更細部的管理遊戲的內容,一般是可用可不用。
但在Forge2D中似乎預設必須要使用它來管理Component。

這裡頭比較棘手的是關於Forge2D中強制使用BodyComponent這個類別,也就是說之前設計的Component不能用....這就是為什麼我要將「paint」和「PositionComponent.render」分開設計的原因,因為「paint」所做的內容不一定只會在「PositionComponent.render」用到....所以,不棘手。
但Forge2D中,對於「尺寸」這件事情的觀念就很奇怪了!

之前的範例中,談到尺寸都是指「pixel」。
但在Forge2D中使用的尺寸怎麼看都不是pixel,畢竟「Ball」的半徑被直接宣告為「5」,(CircleShape()..radius = 5,)。

研究過後發現,原來這是種相對尺寸。
World會被賦予一個同樣也是相對的尺寸,例如如果World是100,而Ball是5,那World就是Ball的二十倍大。
在Forge2D這個系統中,幾乎所有使用到尺寸的地方,都被自動替換成使用這種相對尺寸,所以如果要在Ball裡面畫上一朵花,只需要這樣做....


class Ball extends BodyComponent with TapCallbacks {

  FlowerPainter flowerPainter = FlowerPainter(0, false)..setCenter(0,0);

  @override
  void update(double dt) {
    super.update(dt);
  }

  render(Canvas canvas) {
    super.render(canvas);
    flowerPainter.paint(canvas, Size(9, 9));
  }
  
  ...
}

https://ithelp.ithome.com.tw/upload/images/20240920/20130496l3xcg0sYel.png

花的紋路還會隨著「Ball」的滾動而跟著旋轉。這物理引擎的威力未免太變態!(我喜歡!)


補充1:Forge2D一樣不能當Widget使用,必須要用GameWidget包裝。官方範例推薦的方法如下。


GameWidget.controlled(gameFactory: MyGame4.new)

補充2:在這個官方範例中,遊戲生成瞬間的視窗大小就是遊戲世界大小,但Ball的絕對大小卻依舊不變。也就是說這裡確實存在著某個「幾pixel就對應遊戲中的一個尺度單位」的換算公式,但好像不需要太深究。


上一篇
先來畫圓吧!
下一篇
Forge2D的一些用法...
系列文
用Flutter Flame做遊戲!Live!26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言